// -------------------------------------------------------------------------------------
// Portfolio.m - image Palette instance manager
// Martin D. Flynn, NeXT Computer, Inc.
// You may freely copy, distribute and reuse the code in this example.
// NeXT disclaims any warranty of any kind, expressed or implied, as to its
// fitness for any particular use.
// -------------------------------------------------------------------------------------

#import <stdlib.h>
#import <stdio.h>
#import <string.h>
#import <math.h>
#import <libc.h>
#import	<sys/types.h>
#import	<sys/stat.h>
#import	<sys/time.h>
#import <defaults/defaults.h>
#import <dpsclient/psops.h>
#import <dpsclient/dpsNeXT.h>
#import <objc/objc-runtime.h>
#import <objc/NXBundle.h>
#import <appkit/graphics.h>
#import <appkit/Button.h>
#import <appkit/Panel.h>
#import <appkit/Matrix.h>
#import <appkit/ScrollView.h>
#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/NXEPSImageRep.h>
#import <appkit/SavePanel.h>
#import <appkit/FontManager.h>
#import "ExpandedView.h"
#import "PaletteCell.h"
#import "PaletteMatrix.h"
#import "Portfolio.h"
#import "ImagePortfolio.h"

// -------------------------------------------------------------------------------------

/* static image ids */
static id				openFolder   = (id)nil;
static id				closedFolder = (id)nil;
static id				multiFile    = (id)nil;
static id				stopIcon     = (id)nil;

/* list of instantiated windows */
static id				instanceList = (id)nil;

/* main window location scale */
static NXPoint			winPosScale  = { 0.20, 0.85 };
static NXSize			winOffset = { 64.0, -24.0 };

// -------------------------------------------------------------------------------------
// misc defines
#define	isLOADING		([iconButtonId altImage] == stopIcon)
#define	dftCellROWS		1
#define	dftCellCOLUMNS	5
#define dftFormatNAME	"PortfolioFormat"
#define	X				origin.x
#define	Y				origin.y
#define	W				size.width
#define	H				size.height

// -------------------------------------------------------------------------------------
@implementation Portfolio

// -------------------------------------------------------------------------------------
// internal support

/* return first instance */
+ firstInstance
{
	return (instanceList && [instanceList count])? [instanceList objectAt:0]: (id)nil;
}

// -------------------------------------------------------------------------------------
// internal support

/* set icon button image */
- _setIcon:imageId
{
	if (imageId) [iconButtonId setAltImage:imageId];
	[iconButtonId setImage:[iconButtonId altImage]];
	return self;
}

/* set window title from file name */
- _setWindowTitle:(char*)fileName
{
	char	buff[MAXPATHLEN + 1], *name;

	/* set last file path */
	if (fileName) {
		[NXApp setLastPathNoCopy:filePATH(fileName)];
		name = fileName;
	} else {
		int len = strlen(strcpy(buff, [NXApp lastPath]));
		strcat(buff, ((!len||(buff[len-1]!='/'))?"/UNTITLED":"UNTITLED"));
		name = buff;
	}
  
	/* change window title and add to windows menu */
	[paletteWindow setTitleAsFilename:name];

	return self;
}

/* make save panel */
- _savePanel:(char*)title :(char*)extn
{
	id	savePanel = [SavePanel new];
	[savePanel setTitle:title];
	[savePanel setPrompt:"File:"];
	[savePanel setRequiredFileType:extn];
	[savePanel setDirectory:[NXApp lastPath]];
	return savePanel;
}

/* (re)adjust window size to fit cell size */
- _adjustPreference:(char*)prefBuff
{
	int		rows, cols;
	NXSize	winSize, *cellSize, *gap;
	NXRect	fRect;
	BOOL		rtn;
  
	/* free preference buffer and check for successful */
	rtn = [iconMatrix setPreferences:prefBuff returnRows:&rows cols:&cols];
	free(prefBuff);
	if (!rtn) return self;
  
	/* calculate minimum window size */
	cellSize = [iconMatrix cellSize];
	gap = [iconMatrix intercell];
	[self getWindowSize:&winSize forCellSize:cellSize gap:gap rows:rows cols:cols];
	[paletteWindow getFrame:&fRect];
	fRect.Y += fRect.H - winSize.height;
	fRect.size = winSize;
  
	/* resize and re-display window */
	[paletteWindow placeWindow:&fRect];
	[self windowDidResize:paletteWindow];
	[paletteWindow display];
	[paletteWindow makeKeyAndOrderFront:(id)nil];

	return self;
}

/* (load thread) recursive add directory to image list */
- _addDirectory:(char*)dirPath :(BOOL)chkExtn
{
	struct stat	statBuf;
	if (abortLoad) return (id)nil;
	if (!dirPath || stat(dirPath, &statBuf)) return (id)nil;
	if (statBuf.st_mode & S_IFDIR) sendDIRECTORY(dirPath, self, _cmd, (id)((int)chkExtn));
	else {
		if (chkExtn && !_validExtension(dirPath)) return (id)nil;
		[iconMatrix addImage:dirPath];
	}
	return self;
}

/* (load thread) load from doc file */
- _loadDocument:(char*)fileName :(BOOL)doPref :(BOOL)checkExtension
{
	int		p;
	char	name[MAXPATHLEN + 1], *ptr;
	FILE	*fNum;
  
	/* return if no file name, or if file can't be openned */
	if (!fileName || !(fNum=fopen(fileName,"r"))) return (id)nil;
  
	/* read image paths from file */
	for (*name = 0; !abortLoad && fgets(name, sizeof(name) - 1, fNum);) {
		if (name[(p = strlen(name) - 1)] == '\n') name[p] = 0;
		if (*name != ':') { [self _addDirectory:name :checkExtension]; continue; }
		if (doPref) {
			ptr = NXCopyStringBuffer(name + 1);
			[self mainThreadPerform:@selector(_adjustPreference:) with:(id)ptr wait:YES];
		}
	}
	fclose(fNum);
  
	/* tell matrix to resize */
	[iconMatrix mainThreadPerform:@selector(sizeToCells) wait:NO];
  
	return self;
}

/* (load thread) load file list */
- _doLoad:(fileLIST*)fileList
{
	char		*s, *d, filePath[MAXPATHLEN + 1];
	fileLIST	*fl, *next;
  
	/* return if no list */
	if (!fileList) return self;
  
	/* initial load lock */
	for (fl = fileList; fl;) {

		/* continue loading if not stopped */
		if (!abortLoad) {
			BOOL quit, chkExt, openDoc, setEdit;
	
			/* load list of images (separated by tabs) */
			chkExt  = (fl->flags & 2)?YES:NO;
			openDoc = (fl->flags & 1)?YES:NO;
			setEdit = (fl->flags & 1)?NO:YES;
			for (quit = NO, s = fl->list; !abortLoad && !quit && *s;) {
				for (d = filePath; (*d = *s++) && (*d != '\t'); d++);	// copy til tab or nil
				if (!*d) quit = YES; else *d = 0;						// check end-of-list
				if (strcmp(fileEXT(filePath)+1, docEXTENSION))
					[self _addDirectory:filePath :chkExt];
				else
					[self _loadDocument:filePath :openDoc :NO];
			}

			/* set window edited icon (setEdit only true on the first fileLIST loaded) */
			[paletteWindow mainThreadPerform:@selector(setDocEdited:)
	  				with:(id)((int)setEdit) wait:NO];

		}
	
		/* free list and move to next list of images */
		mutex_lock(loadMutex);
		next = fl->next;
		free(fl->list);
		free(fl);
		fl = next;
		if (!fl) loadList = (fileLIST*)nil;
		mutex_unlock(loadMutex);
	
  	}
  
	/* loading complete */
	[self mainThreadPerform:@selector(_loadComplete) wait:NO];
	return self;
	
}

/* invoked by load thread when loading is complete */
- _loadComplete
{
	mutex_lock(loadMutex);
	if (!loadList) {			// make sure another load hasn't been requested
		abortLoad = YES;
		[self _setIcon:closedFolder];
		[iconMatrix resizeAndDisplay];
	}
	mutex_unlock(loadMutex);
	return self;
}

// -------------------------------------------------------------------------------------
// create a new instance

/* general initialization */
- init
{
	return [self initFromFile:(char*)nil];
}

/* initialize and open file name */
- initFromFile:(const char*)fileName
{
	return [self initFromFile:fileName registerWindow:YES];
}

/* initialize and open file name */
- initFromFile:(const char*)fileName registerWindow:(BOOL)regWindow
{
	id			docView;
	int			rows, cols;
	const char	*dftFmt;
	NXSize		cellSize, scrnSize, gap, clipSize;
	NXRect		docFrame, winRect;
	static int	winCount = 0;

	/* initialize static vars */
	if (!openFolder  ) openFolder   = [NXImage findImageNamed:"openFolder"  ];
	if (!closedFolder) closedFolder = [NXImage findImageNamed:"closedFolder"];
	if (!multiFile   ) multiFile    = [NXImage findImageNamed:"multiFile"   ];
	if (!stopIcon    ) stopIcon     = [NXImage findImageNamed:"stop"        ];
	if (!instanceList) instanceList = listALLOC(1);

	/* init super and add myself to the list of active instances */
	[super init];
	[instanceList addObject:self];
	[self resignActivePortfolio:self];

	/* load nib */
	[NXApp loadNibSection:"Portfolio.nib" owner:self];

	/* set icon button to send message on mouse down */
	fileIcon = (id)nil;
	[iconButtonId sendActionOn:NX_MOUSEDOWNMASK];
	[self _setIcon:closedFolder];
  
	/* file name load list flags */
	abortLoad = YES;
	loadMutex = mutex_alloc();
	loadList  = (fileLIST*)nil;

	/* make a new matrix */
	docView = [paletteScroll docView];
	[docView getFrame:&docFrame];
	[docView getCellSize:&cellSize];
	iconMatrix = [[PaletteMatrix alloc] initFrame:&docFrame];
	[iconMatrix setCellSize:&cellSize];
	[iconMatrix setFont:[[docView cellAt:0:0] font]];
	[iconMatrix setDelegate:self];
	[iconMatrix setTarget:self];
	[iconMatrix setDoubleAction:@selector(showLargeImage:)];
	[paletteScroll setDocView:iconMatrix];
	[paletteScroll setPageScroll:0.0];				// full page scrolling
	[[paletteScroll setHorizScroller:(id)nil] free];	// remove the horiz scroller
	[docView free];									// free the old view

	/* defaults */
	dftFmt = NXReadDefault([NXApp appName], dftFormatNAME);
	if (![iconMatrix setPreferences:(char*)dftFmt returnRows:&rows cols:&cols]) {
		rows = dftCellROWS;
		cols = dftCellCOLUMNS;
	}

	/* calculate new window size and location */
	cellSize = *[iconMatrix cellSize];			// re-read cellSize
	gap = *[iconMatrix intercell];
	[NXApp getScreenSize:&scrnSize];
	[self getWindowSize:&winRect.size forCellSize:&cellSize gap:&gap rows:rows cols:cols];
	winRect.X = floor(scrnSize.width *winPosScale.x)+((float)winCount*winOffset.width );
	winRect.Y = floor(scrnSize.height*winPosScale.y)+((float)winCount*winOffset.height);
	if (winRect.X > (scrnSize.width  - 64.0)) winRect.X = scrnSize.width  - 64.0;
	if (winRect.Y > (scrnSize.height - 24.0)) winRect.Y = scrnSize.height - 24.0;
	winRect.Y -= winRect.H;
	winCount = (++winCount) % 5;

	/* reposition window */
	[paletteWindow placeWindow:&winRect];
	[self windowDidResize:paletteWindow];
	[paletteScroll getContentSize:&clipSize];
	windowOverhead.width  = winRect.W - clipSize.width ;
	windowOverhead.height = winRect.H - clipSize.height;
  
	/* clear window header statistics */
	[self cellResignedSelected:(id)nil];

	/* register window */
	isRegistered = NO;
	allowDrop = regWindow;
	[self _registerWindow];
	[paletteWindow setDelegate:self];
	[paletteWindow setMiniwindowIcon:"doc"];
	[paletteWindow addToEventMask:NX_MOUSEENTEREDMASK];
  
	/* calculate absolute minimum window size */
	minCellSize.width  = 70.0;
	minCellSize.height = 40.0;
	[self getWindowSize:&minWindowSize forCellSize:&minCellSize gap:&gap rows:1 cols:1];
  
	/* load file if specified */
 	 sourceFile = (char*)nil;
	if (fileName) {
		BOOL isDoc=(index(fileName,'\t')||strcmp(fileEXT(fileName),dotDocEXTENSION))?NO:YES;
		if (isDoc) sourceFile = NXCopyStringBuffer(fileName);
		[self loadFileList:fileName :isDoc:!isDoc]; 
	}
	[self _setWindowTitle:sourceFile];

	/* make window key */
	[paletteWindow display];
	[paletteWindow makeKeyAndOrderFront:(id)nil];
  
	/* initialize expanded view */
	[largeView setScrollView:largeScroller];
	[largePanel setDelegate:largeView];

	return self;
}

/* free image Palette window manager */
- free
{
	[paletteWindow free];	// hopefully this takes care of the whole thing
	[largePanel free];
	if (fileIcon) [fileIcon free];
	mutex_free(loadMutex);
	free(sourceFile);
	return self;
}

// -------------------------------------------------------------------------------------
// active portfolio delegate

/* become active portfolio */
- becomeActivePortfolio:sender
{
	isActivePortfolio = YES;
	[paletteWindow becomeMainWindow];
	return self;
}

/* resign active portfolio */
- resignActivePortfolio:sender
{
	isActivePortfolio = NO;
	return self;
}

/* return true if active */
- (BOOL)isActivePortfolio
{
	return isActivePortfolio;
}
  
/* make portfolio active */
+ makeActivePortfolio:(Portfolio*)portObj
{
	[instanceList makeObjectsPerform:@selector(resignActivePortfolio:) with:(id)nil];
	[portObj becomeActivePortfolio:(id)nil];
	return self;
}

/* return active portfolio */
+ activePortfolio
{
	id	pObj = (id)nil;
	int	i = [instanceList count];
	while(i) if ([(pObj = [instanceList objectAt:--i]) isActivePortfolio]) break;
	return pObj;
}

// -------------------------------------------------------------------------------------
// document status

/* return true if there are any unsaved files */
+ (BOOL)isDocEdited
{
	int	i = [instanceList count];
	while(i) if ([[instanceList objectAt:--i] isDocEdited]) return YES;
	return NO;
}

/* return true if this document has been edited */
- (BOOL)isDocEdited
{
	return [paletteWindow isDocEdited];
}

/* return true if loading new images (main thread only!) */
- (BOOL)isLoading
{
	return isLOADING;
}

// -------------------------------------------------------------------------------------
// font

/* return matrix font */
- font
{
	return [iconMatrix font];
}

/* change font */
- setFont:fontObj
{
	[iconMatrix setFont:fontObj];
	return self;
}

// -------------------------------------------------------------------------------------
// button methods

/* show Palette window */
- show:sender
{
	[paletteWindow display];
	[paletteWindow makeKeyAndOrderFront:(id)nil];
	return self;
}

/* open file */
- open:sender
{
	return self;
}
  
/* save file names (called by first responder) */
- save:sender
{
  
	/* get file name to save */
	if (!sourceFile) {
		const char *temp;
		id pSave = [self _savePanel:"Save Image Palette" :docEXTENSION];
		if (![pSave runModalForDirectory:[NXApp lastPath] file:""]) return self;
		if (!(temp=[pSave filename])) return self;
		sourceFile = NXCopyStringBuffer(temp);
	}
  
	/* open , write data, and close */
	[iconMatrix saveToFile:sourceFile];
	[self _setWindowTitle:sourceFile];

	return self;
}

/* save file names (called by first responder) */
- saveAs:sender
{
	if (sourceFile) { free(sourceFile); sourceFile = (char*)nil; }
	return [self save:sender];
}

/* save defaults */
- saveDefaults:sender
{
	char	buff[512];
	NXWriteDefault([NXApp appName], dftFormatNAME, [iconMatrix getPreferenceString:buff]);
	return self;
}

// -------------------------------------------------------------------------------------
// calculate window size overhead
// - This method needs to know the layout of the screen to properly calculate its size

- getWindowSize:(NXSize*)windowSize forCellSize:(NXSize*)cellSize gap:(NXSize*)gapSize
		rows:(int)rows cols:(int)cols
{
	NXRect	fRect, cRect;
	NXSize	actualSize;

	/* calculate actual size of content area */
	actualSize.width  = (cellSize->width  + gapSize->width ) * cols;
	actualSize.height = (cellSize->height + gapSize->height) * rows;

	/* calc size of scrollView */
	[[paletteScroll class] getFrameSize:&cRect.size forContentSize:&actualSize
		horizScroller:([paletteScroll horizScroller]?YES:NO)
		vertScroller:([paletteScroll vertScroller]?YES:NO)
		borderType:[paletteScroll borderType]];

	/* calc size of window contentView */
	[windowHeader getFrame:&fRect];
 	 cRect.H += fRect.H;
  
	/* calc window size */
	cRect.X = cRect.Y = 0.0;
	[[paletteWindow class] getFrameRect:&fRect forContentRect:&cRect
  		style:[paletteWindow style]];
	*windowSize = fRect.size;

	return self;
}

/* get current displayed row/col count */
- getDisplayedRows:(int*)rows cols:(int*)cols
{
	NXSize	scSize, *cellSize = [iconMatrix cellSize], *gap = [iconMatrix intercell];
	[paletteScroll getContentSize:&scSize];
	*rows = (int)rint(scSize.height / (cellSize->height + gap->height));
	*cols = (int)rint(scSize.width  / (cellSize->width  + gap->width ));
	return self;
}

/* return current cell size */
- (NXSize*)cellSize
{
	return [iconMatrix cellSize];
}

// -------------------------------------------------------------------------------------
// show selected cell enlarged image (double action from matrix)

/* matrix doubleAction */
- showLargeImage:sender
{
	id		imageCell = [iconMatrix selectedCell];
  
	/* hide large panel and return if no selected cell */
	[largePanel orderOut:self];
	if (!imageCell) return self;
  
	/* show enlarged image */
	[self showImage:[imageCell image] title:(char*)[imageCell cellTitle]];
  
	return self;
}

/* show specified image */
- showImage:imageId title:(char*)title
{
	BOOL			hScroll, vScroll;
	NXRect			cRect, fRect;
	static NXSize	maxSize, scrSize = { 0.0, 0.0 };

	/* init max screen size */
	if (!scrSize.width) {
		[NXApp getScreenSize:&scrSize];
		maxSize.width  = floor(scrSize.width  * 0.80);
		maxSize.height = floor(scrSize.height * 0.80);
	}

	/* hide large panel and return if no image */
	[largePanel orderOut:self];
	if (!imageId) return self;

	/* set panel image */
	[largeView setImage:imageId];
	[largePanel setTitle:title];
  
	/* resize panel */
	hScroll = vScroll = NO;
	[largeView getFrame:&cRect];
	[Window getFrameRect:&fRect forContentRect:&cRect style:[largePanel style]];
	if ((fRect.W >= scrSize.width) || (fRect.H >= scrSize.height)) {
 	   fRect.size = maxSize;
		[largeView windowWillResize:largePanel toSize:&fRect.size];
		[Window getContentRect:&cRect forFrameRect:&fRect style:[largePanel style]];
		hScroll = vScroll = YES;
	}
	[largeScroller setHorizScrollerRequired:hScroll];
	[largeScroller setVertScrollerRequired:vScroll];
	cRect.X = cRect.Y = 0.0;
	[largeScroller setFrame:&cRect];
	[largePanel sizeWindow:cRect.W :cRect.H];
  
	/* show enlarged image */
	[largePanel center];
	[largePanel display];
	[largePanel makeKeyAndOrderFront:self];
  
	return self;
}

// -------------------------------------------------------------------------------------
// pass file list to workspace manager

/* send files to workspace manager for dragging */
- iconDrag:sender
{
	NXRect	bFrame, rect;
	char	*fullPath;
  
	/* ignore if not called by proper button */
	if (sender != iconButtonId) return self;						// ignore any imposters
  
	/* check for STOP button */
	if (isLOADING) { abortLoad = YES; return self; }				// stop button pressed
  
	/* ignore if no files selected */
	if (!(fullPath = [iconMatrix selectedCellPaths])) return self;	// ignore if no files
  
	/* drag selected files */
	[iconButtonId getBounds:&bFrame];
	rect.W = 48.0;
	rect.H = 48.0;
	rect.X = (bFrame.W - rect.W) / 2.0;
	rect.Y = (bFrame.H - rect.H) / 2.0;
	[self _unregisterWindow];										// unregister myself
	[iconButtonId dragFile:fullPath fromRect:&rect slideBack:YES event:[NXApp currentEvent]];
	[self _registerWindow];											// re-register myself
  
	/* free file list */
	free(fullPath);
  
	return self;
}

// -------------------------------------------------------------------------------------
// PaletteMatrix delegate methods

/* (main thread) load tabbed file list */
- loadFileList:(const char*)fileNames :(BOOL)openDoc :(BOOL)chkExtn
{
	fileLIST	*fl;
  
	/* return if no files to load */
	if (!fileNames) return self;
  
	/* allocate and fill file list structure */
	fl = (fileLIST*)malloc(sizeof(fileLIST));
	fl->list  = NXCopyStringBuffer(fileNames);
	fl->flags = (chkExtn?2:0) | (openDoc?1:0);
	fl->next  = (fileLIST*)nil;
  
	/* load images (fork new thread if necessary) */
	mutex_lock(loadMutex);
	if (loadList) loadList->next = fl;	// load already in progress
	else {
		abortLoad = NO;						// reset abort flag
		[self _setIcon:stopIcon];			// setup stop button
		[iconMatrix clearSelectedCell];		// deselect any selected cells
		[self forkPerform:@selector(_doLoad:) with:(id)fl detach:YES];
	}
	loadList = fl;
	mutex_unlock(loadMutex);
  
	return self;
}

/* set image statistics (only called when a cell is (de)selected) */
- clearImageStats:iconImage		// 'closedFolder' or 'multiFile' only
{
	[iconPathId setStringValue:""];
	[iconSizeId setStringValue:""];
	[self _setIcon:iconImage];
	return self;
}

/* set image statistics */
- setImageStats:imageCell
{
	NXRect	bBox;
	char	buff[256];
  
	/* clear stats if no cell */
	if (!imageCell) { [self clearImageStats:closedFolder]; return self; }
  
	/* fill info */
	[iconPathId setStringValueNoCopy:[imageCell imagePath]];
	[[imageCell image] getSize:&bBox.size];
	sprintf(buff, "%.0f x %.0f", bBox.W, bBox.H);
	[iconSizeId setStringValue:buff];
  
	/* set icon representation */
	if (fileIcon) [fileIcon free];
	fileIcon = [[Application workspace] getIconForFile:(char*)[imageCell imagePath]];
	[self _setIcon:fileIcon];

	return self;
}

/* image became selected */
- cellBecameSelected:imageCell
{
	[largePanel orderOut:self];	// hide large panel
	if ([iconMatrix selectedCellCount] <= 1) [self setImageStats:imageCell];
	else { [largeView setImage:(id)nil]; [self clearImageStats:multiFile]; }
	return self;
}

/* image became selected */
- cellResignedSelected:imageCell
{
	if ([iconMatrix selectedCellCount] > 1) [self clearImageStats:multiFile];
	else [self setImageStats:[iconMatrix selectedCell]];
	return self;
}
  
// -------------------------------------------------------------------------------------
// Workspace manager icon dragging support
#define	isSHIFT(S)	((([NXApp currentEvent]->flags) & (S))? YES : NO)
static char			*wsTiffName = (char*)nil;

/* keyboard shifts states are now current, do load */
- _delayLoad:sender
{
	[self loadFileList:wsTiffName:NO:(isSHIFT(NX_CONTROLMASK)?NO:YES)];
	return self;
}

/* indicate we accept dragging */
- (BOOL)prepareForDragOperation:sender
{
    if (!wsTiffName) return NO;
	return YES;
}

/* files dropped */
- (BOOL)performDragOperation:sender
{
	[self _setIcon:(id)nil];
    if (!wsTiffName) return NO;
	[NXApp activateSelf:YES];
	[paletteWindow makeKeyAndOrderFront:(id)nil];
	[self perform:@selector(_delayLoad:) with:self afterDelay:10 cancelPrevious:NO];
	return YES;
}

/* file icon exited */
// - reset the open file folder icon
- draggingExited:sender
{
	[self _setIcon:(id)nil];
    return self;
}

/* file icon entered */
// - save the list of file names and the representing icon image
- (NXDragOperation)draggingEntered:sender
{
	char		*fileList;
	int			fileListLen;
    Pasteboard	*pb = [sender draggingPasteboard];
	if (wsTiffName) { free(wsTiffName); wsTiffName = (char*)nil; }
	if ([pb readType:NXFilenamePboardType data:&fileList length:&fileListLen]) {
		wsTiffName = NXCopyStringBuffer(fileList);
		return NX_DragOperationCopy;
	}
    return NX_DragOperationNone;
}

/* file icon moved to location */
- (NXDragOperation)draggingUpdated:sender
{
    if (!wsTiffName) return NX_DragOperationNone;
    [iconButtonId setImage:openFolder];
    return NX_DragOperationCopy;
}

/* register window with workspace manager */
- _registerWindow
{
    NXAtom	pbsTypes[1];
    if (!allowDrop || isRegistered) return self;
    pbsTypes[0] = NXFilenamePboardType;
    [paletteWindow registerForDraggedTypes:pbsTypes count:1];
    isRegistered = YES;
    return self;
}

/* unregister window with workspace manager */
- _unregisterWindow
{
    if (!allowDrop || !isRegistered) return self;
    [paletteWindow unregisterDraggedTypes];
    isRegistered = NO;
    return self;
}

// -------------------------------------------------------------------------------------
// window delegate methods

/* window is resizing */
- windowWillResize:windowId toSize:(NXSize*)newSize
{
	float		rows, cols, mRow, mCol;
	NXSize		size, minSize, oneCellSize;
	NXSize		*cellSize = [iconMatrix cellSize], *gap = [iconMatrix intercell];
  
	/* actual cell size */
	oneCellSize = *cellSize;
	oneCellSize.width  += gap->width ;
	oneCellSize.height += gap->height;

	/* absolute minimum size */
	if (newSize->width  < minWindowSize.width ) newSize->width  = minWindowSize.width ;
	if (newSize->height < minWindowSize.height) newSize->height = minWindowSize.height;

	/* allow any window size if Alternate is pressed */
	if (isSHIFT(NX_ALTERNATEMASK)) {
		if (isLOADING) {	// retain original size
			NXRect wFrame;
			[windowId getFrame:&wFrame];
			*newSize = wFrame.size;
			return self;
		}
		[paletteScroll getContentSize:&size];
		cols = size.width  / oneCellSize.width ;
		rows = size.height / oneCellSize.height;
		minSize.width  = rint((minCellSize.width +gap->width )*cols)+windowOverhead.width ;
		minSize.height = rint((minCellSize.height+gap->height)*rows)+windowOverhead.height;
		if (newSize->width  < minSize.width ) newSize->width  = minSize.width;
		if (newSize->height < minSize.height) newSize->height = minSize.height;
		return self;
	}
  
	/* window size must hold at least one cell */
	minSize.width  = windowOverhead.width  + oneCellSize.width ;
	minSize.height = windowOverhead.height + oneCellSize.height;
	if (newSize->width  < minSize.width ) newSize->width  = minSize.width ;
	if (newSize->height < minSize.height) newSize->height = minSize.height;

	/* size window to cell boundary */
	cols = rint((newSize->width  - windowOverhead.width ) / oneCellSize.width );
	rows = rint((newSize->height - windowOverhead.height) / oneCellSize.height);
	mCol = ceil((minWindowSize.width  - windowOverhead.width ) / oneCellSize.width );
	mRow = ceil((minWindowSize.height - windowOverhead.height) / oneCellSize.height);
	if (cols < mCol) cols = mCol;
	if (rows < mRow) cols = mRow;
	newSize->width  = windowOverhead.width  + cols * oneCellSize.width ;
	newSize->height = windowOverhead.height + rows * oneCellSize.height;

	return self;
}

/* window is resizing */
- windowDidResize:windowId
{
	float		oldRows, oldCols;
	NXRect	sFrame, matFrame, winFrame;
	NXSize	scSize, *cellSize = [iconMatrix cellSize], *gap = [iconMatrix intercell];

	/* ignore if loading is in progress */
	if (isSHIFT(NX_ALTERNATEMASK) && isLOADING) return self;

	/* get new window size */
	[[windowId contentView] getFrame:&winFrame];

	/* reposition header */
	[windowHeader getFrame:&sFrame];
	winFrame.H -= sFrame.H;
	sFrame.Y = winFrame.H;
	sFrame.W = winFrame.W;
	[windowHeader setFrame:&sFrame];
	headerSize = sFrame.size;
  
	/* resize iconPathId text field */
	[iconPathId getFrame:&sFrame];
	sFrame.W = headerSize.width - sFrame.X;
	[iconPathId setFrame:&sFrame];

	/* save current number of displayed rows/cols */
	[paletteScroll getContentSize:&scSize];
	oldCols = scSize.width  / (cellSize->width  + gap->width );
	oldRows = scSize.height / (cellSize->height + gap->height);
  
	/* resize scroller */
	[paletteScroll getFrame:&sFrame];
	sFrame.size = winFrame.size;
	sFrame.Y = 0.0;
	sFrame.X = 0.0;
	[paletteScroll setFrame:&sFrame];
  
	/* resize matrix within scroller */
	[paletteScroll getContentSize:&scSize];
	[iconMatrix getFrame:&matFrame];
	matFrame.size = scSize;
	[iconMatrix setFrame:&matFrame];

	/* if alternate key is pressed, then resize cells */
	if (isSHIFT(NX_ALTERNATEMASK)) {
		NXSize	size;
 	   size.width  = rint(matFrame.W / oldCols) - gap->width;
 	   size.height = rint(matFrame.H / oldRows) - gap->height;
		[iconMatrix setCellSize:&size];
	}
  
	/* size matrix view to fit cells */
	[iconMatrix sizeToCells];
  
	return self;
}

/* window will close */
- windowWillClose:windowId
{
  
	/* delay window close if still loading */
	abortLoad = YES;
	if (isLOADING) {
		[windowId perform:@selector(performClose:) with:self
			afterDelay:250 cancelPrevious:NO];
		return (id)nil;
	}
  
	/* check for saved */
	if ([windowId isDocEdited]) {
		int  rtn;
		const char *save, *no, *cncl, *savf;
  		save = NXLocalizedString("Save", (char*)nil,(char*)nil);
  		no   = NXLocalizedString("No", (char*)nil,(char*)nil);
  		cncl = NXLocalizedString("Cancel", (char*)nil,(char*)nil);
		if (sourceFile) {
			char *name = fileNAME(sourceFile);
			savf = NXLocalizedString("Save changes to %s?", (char*)nil,(char*)nil);
			rtn = NXRunAlertPanel(save, savf, save, no, cncl, name);
			free(name);
		} else {
			savf = NXLocalizedString("Save changes to UNTITLED?", (char*)nil,(char*)nil);
			rtn = NXRunAlertPanel(save, savf, save, no, cncl);
		}
		if (rtn == NX_ALERTOTHER) return (id)nil;					// Cancel
		if (rtn == NX_ALERTDEFAULT) [self save:(id)nil];			// Save (else No)
	}

	/* unregister / remove / and free */
	[self _unregisterWindow];
	[instanceList removeObject:self];
	return [NXApp delayedFree:self];
  
}

/* window became key */
- windowDidBecomeKey:windowId
{
	[[self class] makeActivePortfolio:self];
	[windowId makeFirstResponder:iconMatrix];
	[[FontManager new] setSelFont:[iconMatrix font] isMultiple:NO];
	return self;
}

@end
